基于Nginx的应用鉴权架构

使用Nginx及其鉴权子模块,实现第三方访问鉴权。

背景

当我们需要为后端服务的访问进行鉴权,尤其是为一些自身不带鉴权且难以为其本身添加权限校验代码时,比如Kubernetes封装的docker容器登陆服务与前端结合的这类Web伪Terminal、再比如Kubeflow Jupyter Notebook这类WebIDE,往往需要一个组件充当Gateway用以判断请求是否能转发到后端或是将其拒绝。
在此背景下,通常已经有完整的鉴权服务器,需要做的只是将鉴权服务器与后端服务结合起来。

选型

Gateway App

首先,当然可以自己写一个Gateway,接收所有的请求后,调用鉴权服务,并决定放行与否。
但这种方案需要考虑开发和维护的时间成本及难度、以及性能问题。

Proxy

常见的代理有Haproxy及Nginx,其中Nginx的子请求模块(auth_request module)可以在请求转发前发起子请求,因此在上述应用场景中可以作为简捷的Gateway。

反向代理

架构

当Nginx接收到用户请求时,首先利用收集到的用户信息向鉴权服务器发起鉴权子请求,根据鉴权服务器的返回结果:

  1. 如果响应代码为20X,则转发到真实的后台服务,同时可带上从鉴权服务器获取的一些信息。
  2. 如果响应代码为401或403,以对应的代码拒绝客户端的请求。
  3. 如果是其他代码,则视为后端错误。
    基本流程如下图所示:
    nginx_auth

时序如下图所示:
nginx_auth

配置实例 - Kubeflow Jupyter Notebook

Kubeflow Jupyter Notebook是一种机器学习的WebIDE,通过Jupyter Hub生成,外界通过Websocket接口与其交互。
在Kubernetes环境中,如果对集群外(如浏览器)提供访问,可以通过NodePort的方式对外暴露其宿主机IP及端口,同时根据其namespace及名称来访问,如:

1
2
http://<Node的IP>:<NodePort>/notebook/<notebook所在的namespace名称>/<notebook名称>
e.g. http://www.example.com/notebook/test-namespace/test-notebook

但这种方式每个Notebook都需要占用一个Node端口,较为耗费端口资源。
而如果通过Nginx作为Gateway的方式来对外提供服务,只需暴露Node的一个Nginx Service端口,即可处理全部请求;Nginx Pod鉴权通过后再根据请求的参数及Notebook的内部地址(ClusterIP+端口)转发请求。
Nginx的配置如下:

鉴权服务器定义

指定鉴权服务器的地址,需要确保Nginx能访问的地址,如果鉴权服务在Kubernetes集群外部则需保证网络路由能到达,如果在内部则需有对应Service对集群内提供ClusterIP的访问方式。

1
2
3
upstream authserver {
server <addess of auth server>:<port>
}

主请求定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server{
......
location / {
set $user_token $http_user_token;
if ($request_uri ~* (notebook\/([\w-]*)) ) {
set $user_namespace $2;
}
if ($request_uri ~* (notebook\/([\w-]*\/([\w-]*))) ) {
set $user_notebook $3;
}
set $backend_realip $remote_addr;
set $auth_request_uri "http://authserver/api/auth/namespaces/$backend_namespace/notebooks/$backend_notebook";
auth_request /auth;
# get header "notebook address" from auth request
proxy_http_version 1.1;
proxy_read_timeout 1800s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
# forward to notebook service
proxy_pass http://$backend_notebook.$backend_namespace.svc.cluster.local:80$request_uri;
}
}
子请求定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   server {
......
location = /auth {
internal;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Token $user_token;
proxy_pass $auth_request_uri;
}
......
}
其他部分
1
2
3
4
5
6
7
   server {
listen 80;
underscores_in_headers on;
client_max_body_size 0;
chunked_transfer_encoding on;
......
}
完整定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
upstream authserver {
server <addess of auth server>:<port>
}
server {
listen 80;
underscores_in_headers on;
client_max_body_size 0;
chunked_transfer_encoding on;
location = /auth {
internal;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Token $user_token;
proxy_pass $auth_request_uri;
}
location / {
set $user_token $http_user_token;
if ($request_uri ~* (notebook\/([\w-]*)) ) {
set $user_namespace $2;
}
if ($request_uri ~* (notebook\/([\w-]*\/([\w-]*))) ) {
set $user_notebook $3;
}
set $backend_realip $remote_addr;
set $auth_request_uri "http://authserver/api/auth/namespaces/$backend_namespace/notebooks/$backend_notebook";
auth_request /auth;
# get header "notebook address" from auth request
proxy_http_version 1.1;
proxy_read_timeout 1800s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
# forward to notebook service
proxy_pass http://$backend_notebook.$backend_namespace.svc.cluster.local:80$request_uri;
}
}

另外需要注意的是,Nginx的默认模块中不包含该模块,因此需要--with-http_auth_request_module参数,可以自己编译nginx镜像,也可以用此镜像:

1
docker pull alterway/nginx:1.10-auth-request

参考资料

Module ngx_http_auth_request
Docker hub Image: Nginx with auth_request